﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace NMF.Optimizations.Optimizer
{
    public class GreedyOptimizer : IOptimizer
    {
        private static GreedyOptimizer instance;

        public static GreedyOptimizer Instance
        {
            get
            {
                if (instance == null) instance = new GreedyOptimizer();
                return instance;
            }
        }

        public IOptimizationScenario Optimize(IOptimizationScenario initialScenario, IScenarioEvaluator evaluator)
        {
            if (initialScenario == null) throw new ArgumentNullException("initialScenario");
            if (evaluator == null) throw new ArgumentNullException("evaluator");

            initialScenario.Cost = evaluator.EvaluateScenario(initialScenario);

            return GreedyExploreScenario(initialScenario, initialScenario.Optimization, evaluator);
        }

        private static IOptimizationScenario GreedyExploreScenario(IOptimizationScenario currentScenario, Optimization optimization, IScenarioEvaluator evaluator)
        {
            double lowestCost = optimization.ForceModifications ? double.PositiveInfinity : currentScenario.Cost;
            IOptimizationScenario bestScenario = currentScenario;
            IOptimizationModification bestMod = null;

            var mods = optimization.GetCurrentModifications().Distinct().ToArray();
            foreach (var modification in mods)
            {
                if (!modification.Apply()) continue;
                var modifiedScenario = currentScenario.Fork(modification);
                var cost = evaluator.EvaluateScenario(modifiedScenario);
                modifiedScenario.Cost = cost;
                if (cost < lowestCost)
                {
                    lowestCost = cost;
                    bestScenario = modifiedScenario;
                    bestMod = modification;
                }
                modification.Reverse();
            }
            if (bestScenario != currentScenario)
            {
                if (!bestMod.Apply()) throw new InvalidOperationException("A modification has been cancelled. Please check the reversibility of your modifications.");
                return GreedyExploreScenario(bestScenario, optimization, evaluator);
            }
            else
            {
                return currentScenario;
            }
        }
    }

}
